# Copyright (c) 2011 Andrea Spadaccini <andrea.spadaccini@gmail.com>
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
# General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
# 02110-1301, USA.

"""Biometric backends.

This file contains the biometric backends that can be used for authentication
in biometric4net.
"""

from os.path import dirname, join
from subprocess import Popen, PIPE
from tempfile import NamedTemporaryFile
from operator import mul

__author__ = "Andrea Spadaccini"
__license__ = "GPL v3 or later"
__email__ = "andrea.spadaccini@gmail.com"

__all__ = ["DummyBackend", 
           "LiaRalBackend",
           "SumMatchScoresMultiBiometricBackend",
           "MultiplyMatchScoresMultiBiometricBackend"]

class BiometricBackend:
    """Base class for all the biometric backends."""
    def compute_score(self, data, identity):
        raise NotImplementedError

class MultiBiometricBackend:
    """Base class for the multi-biometric backends."""
    def __init__(self, backends):
        """Creates the multimodal back-end. Expects in input a list of
        instances of classes derived from BiometricBackend"""
        self.backends = backends

    def compute_score(self, data_array, identity):
        """Computes the match score. Expects in input the identity and an
        array of biometric data, in which each slot is associated with the
        backend that is in the same position in the backends arry."""
        scores = list()
        for d, backend in zip(data_array, self.backends):
            scores.append(backend.compute_score(d, identity))

        return self.fuse_scores(scores)

    def fuse_scores(self, scores):
        """Gathers together the scores and returns the final match score
        according to the fusion strategy. Must be implemented by children
        classes"""
        raise NotImplementedError

class SumMatchScoresMultiBiometricBackend(MultiBiometricBackend):
    """Class that implements a match-score level fusion strategy that sums all
    the match scores"""
    def fuse_scores(self, scores):
        return sum(scores)

class MultiplyMatchScoresMultiBiometricBackend(MultiBiometricBackend):
    """Class that implements a match-score level fusion strategy that
    multiplies all the match scores"""
    def fuse_scores(self, scores):
        if not scores:
            return 0

        return reduce(mul, scores)

class DummyBackend(BiometricBackend):
    """Dummy backend that always returns a score of 1."""
    def compute_score(self, data, identity):
        print "Dummy backend! Identity: %s" % identity
        return {'score': 1}

class LiaRalBackend(BiometricBackend):
    """Backend that uses the ALIZE/LIA_RAL text-independent
    speaker recognition system for the score computation.

    Plese set the LIARAL_PATH variable to the path where LIA_RAL is installed.
    This class expects the installation directory of LIA_RAL to be laid out as
    such:
    
        bin/           directory containing the LIA_RAL binaries and scripts
        data/uct10     directory containing the LIA_RAL config files

    Please see the compute_score.sh script for further info on the LIA_RAL
    directory hierarchy."""
    LIARAL_PATH = "/usr/local/share/liaral"
    SPKREC_CMD = join(LIARAL_PATH, "bin", "compute_score.sh")

    def compute_score(self, data, identity):
        """
        Computes the biometric score of the given data using LIA_RAL.

        @param data: A pyamf.amf3.ByteArray instance
        @param identity: An identity in the database (actually, an integer
        ranging from 1 to 50 for the UCT10 DB)
        """

        # Save data
        fp = NamedTemporaryFile("wb+")
        fp.write(data.getvalue())

        # Compute score
        output = Popen([self.SPKREC_CMD, fp.name, identity], stdout=PIPE,
                cwd=dirname(self.SPKREC_CMD)).communicate()[0]
        print output
        print fp.name

        # The actual score is the fourth element of the last but one row
        score = output.split('\n')[-2].split()[4]

        # Close (and thus destroy) the temporary file
        #fp.close()

        return {'score': score}
